import chisel3._
import chisel3.util._
import config.Configs._
import config.CacheConfig._
import config.GenCtrl

// 地址转换模块：实现虚拟地址到物理地址的转换，支持直接映射、页表映射、直达模式
class AddrTran extends Module{
  // IO端口定义
  val io=IO(new Bundle { //TODO:打包成bundle
    val tran_en=Input(Bool())                // 地址转换使能信号
    val vaddr=Input(UInt(ADDR_WIDTH.W))      // 输入虚拟地址
    val paddr=Output(UInt(ADDR_WIDTH.W))     // 输出物理地址
    val direct_uncache=Output(Bool())        // 直接映射下uncache标志（组合逻辑，立即给出）
    val page_uncache  =Output(Bool())        // 页表映射下uncache标志（下一拍给出，依赖tran_en）
    val mode=Input(new Bundle {              // 地址模式
      val pg=Bool()                          // 页表模式使能
      val da=Bool()                          // 直达模式使能
    })
    val plv =Input(UInt(2.W))                // 当前特权级
    val datx=Input(UInt(2.W))                // 直达模式下的datx
    val dmw0=Input(new csr_dmw_bundle())     // DMW0寄存器
    val dmw1=Input(new csr_dmw_bundle())     // DMW1寄存器
    // TLB相关信号（可选）
    val tlb_ps4kb=if(GenCtrl.USE_TLB) Some(Input(Bool())) else None // TLB页大小4KB标志
    val tlb_ppn  =if(GenCtrl.USE_TLB) Some(Input(UInt(TAG_WIDTH.W))) else None // TLB物理页号
    val tlb_mat  =if(GenCtrl.USE_TLB) Some(Input(UInt(2.W))) else None         // TLB属性
    val is_direct_map=Output(Bool())        // 当前是否为直接映射
    val is_page_map  =Output(Bool())        // 当前是否为页表映射
  })

  // 保存上一拍的虚拟地址（页表映射时用）
  val vaddr_next=RegNext(io.vaddr)

  // DMW0/DMW1特权级使能判断
  val dmw0_plv_en=((io.dmw0.plv0.asBool&&io.plv===0.U)
                ||(io.dmw0.plv3.asBool&&io.plv===3.U))
  val dmw1_plv_en=((io.dmw1.plv0.asBool&&io.plv===0.U)
                ||(io.dmw1.plv3.asBool&&io.plv===3.U))

  // DMW0/DMW1直接映射使能条件
  val dmw0_en =dmw0_plv_en&&io.mode.pg&&(io.dmw0.vseg.asUInt===io.vaddr(31,29)) // DMW0直接映射
  val dmw1_en =dmw1_plv_en&&io.mode.pg&&(io.dmw1.vseg.asUInt===io.vaddr(31,29)) // DMW1直接映射

  // 页表映射/直接映射判定
  val page_map  = io.mode.pg && ~dmw0_en && ~dmw1_en // 页表映射：pg模式且不是dmw0/dmw1
  val direct_map= ~page_map                          // 直接映射：否则
  io.is_page_map:=page_map
  io.is_direct_map:=direct_map

  // 直接映射物理地址生成
  val direct_map_addr=(
      (Fill(ADDR_WIDTH,dmw0_en)&Cat(io.dmw0.pseg , io.vaddr(28,0))) // DMW0映射
    | (Fill(ADDR_WIDTH,dmw1_en)&Cat(io.dmw1.pseg , io.vaddr(28,0))) // DMW1映射
    | (Fill(ADDR_WIDTH,~(dmw0_en||dmw1_en))  &     io.vaddr)        // 其他情况直接用虚拟地址
  )

  // 页表映射物理地址生成
  // TLB页大小为4KB时，ppn拼接低12位；否则拼接低22位
  val page_map_addr=Mux(io.tlb_ps4kb.getOrElse(false.B),Cat(io.tlb_ppn.getOrElse(0.U),vaddr_next(11,0)),
                                                        Cat(io.tlb_ppn.getOrElse(0.U)(19,10),vaddr_next(21,0)))
  // 物理地址选择：优先级为直达 > 直接映射 > 页表映射
  io.paddr:=(
      (Fill(ADDR_WIDTH,RegNext(direct_map))&RegNext(direct_map_addr)) // 直接映射
    | (Fill(ADDR_WIDTH,RegNext(page_map  ))&page_map_addr)            // 页表映射
    | (Fill(ADDR_WIDTH,RegNext(io.mode.da))&RegNext(io.vaddr))        // 直达模式
  )

  // 直接映射uncache条件（组合逻辑，立即给出）
  val direct_map_uncache=((io.mode.da&&io.datx===0.U)                // 直达模式且datx为0
                        ||(dmw0_en&&io.dmw0.mat===0.U)              // DMW0映射且mat为0
                        ||(dmw1_en&&io.dmw1.mat===0.U))             // DMW1映射且mat为0

  io.direct_uncache:=direct_map_uncache
  // 页表映射uncache条件（下一拍给出，依赖tran_en）
  io.page_uncache:=(RegNext(page_map)&&io.tlb_mat.getOrElse(1.U)===0.U)&&RegNext(io.tran_en)
  //NOTE:两个结果并不是同一拍输出，为了减少uncache条件下pf重取指的惩罚

  dontTouch(io.direct_uncache)
  dontTouch(io.page_uncache)
} 